#include "lpr.h"

#include <time.h>

#include "hw_app_log.h"
#include "hw_app_config.h"
#include "cv_utils.h"
#include "spdlog/stopwatch.h"
#include "string_utils.hpp"

namespace gc
{
    GCLpr::GCLpr()
    {
        m_client = nullptr;
        // MNN in cpu
        // m_forwarder = CreateDrpApp();
        // MNN in nnie
        m_nnie.Init();
        NnCreateParam param = {};
        param.batchNum = 1;
        param.loadModeModel= 1;
        param.modelPath = (char *)gc_app_config.base_config.alg_config.local_lpr_model_path.c_str();
        int ret = m_nnie.LoadModel(&param);
        if (ret != HW_OK) {
            LOG_ERROR("init nnie load model for lpr error=%d", ret);
        }
        ret = m_nnie.ParamInit();
        if (ret != HW_OK) {
            LOG_ERROR("init nnie init param for lpr error=%d", ret);
        }
    }

    GCLpr::~GCLpr(void)
    {
        delete m_client;
    }

    httplib::Client* GCLpr::httpClient(void)
    {
        if (m_client != nullptr) {
            return m_client;
        }
        // httplib::Client client(HWApp::HttpProxy::HTTP_PROXY_SOCK);
        m_client = new httplib::Client("unix:/tmp/http_proxy_connect.socket");
        m_client->set_default_headers({ { "Host", gc_app_config.base_config.lpr_host.c_str() }, { "Proxy-Connection", "keep-alive" }, { "Accept-Encoding", "gzip, deflate" } });
        m_client->set_read_timeout(10);
        m_client->set_write_timeout(10);
        m_client->set_keep_alive(true);
        m_client->set_compress(true);
        m_client->set_logger([](const httplib::Request& req, const httplib::Response& res) {
            LOG_INFO("http request: method=%s, %s%s", req.method.c_str(), gc_app_config.base_config.lpr_host.c_str(), req.path.c_str());
            LOG_INFO("http response: status=%d, body=%s", res.status, res.body.c_str());
        });
        return m_client;
    }

    /**
     * @brief 
     * 
        configs 配置参数
            - image_save: 1 or 0 是否保存图片
            - return_box: 1 or 0 只有检测框时是否返回
            - filter_error_plate: 1 or 0 是否过滤不符合规则车牌
            - return_src: 1 or 0 是否返回原始识别车牌结果
            - priority_province_chinese: 优先省份汉字
            - priority_province_char: 优先省份字母
            - chinese_thres: 替换优先省份时的阈值 0-100
            - chinese_thres_for_char: 根据优先省份字母替换优先省份时的阈值 0-100
            - need_detect: 是否需要进行检测（如果不需要检测，则直接进行转角度和识别）
            - need_transformer: 是否需要进行转角度操作
            - detect_model: mtcnn / retina
            - plate_resize_type: resize / perspective(only when detect_model == retina)
            - double_detect_by_default_model: 1 or 0  是否使用默认模型进行二次识别补充
        '''

    * @param img 
    * @return nlohmann::json 
    */
    nlohmann::json GCLpr::lpr_remote(cv::Mat &img)
    {
        spdlog::stopwatch sw;

        std::string base64 = Mat2Base64(img, ".jpg");
        base64.insert(0, "data:image/jpeg;base64,");
        nlohmann::json body = {
            {"save", 0},
            {"image_data", base64.c_str()},
            {"priority_province_chinese", gc_app_config.base_config.alg_config.priority_province_chinese},
            {"priority_province_char", gc_app_config.base_config.alg_config.priority_province_char},
            {"chinese_thres", gc_app_config.base_config.alg_config.chinese_thres},
            {"chinese_thres_for_char", gc_app_config.base_config.alg_config.chinese_thres_for_char},
            {"detect_model", "retina"},
            {"plate_resize_type", "perspective"},
            {"double_detect_by_default_model", 1},
        };
        std::string content = body.dump();

        struct timespec time1 = {0, 0};
        std::string ts = to_string(time1.tv_sec * 1000 + time1.tv_nsec / 1000000);
        auto res = httpClient()->Post((gc_app_config.base_config.lpr_api + "?ts=" + ts).c_str() , content, "application/json");
        int retry_time = 0;
        while ((res == nullptr || res->status != 200) && retry_time < 3) {
            usleep(100000);
            res = httpClient()->Post((gc_app_config.base_config.lpr_api + "?ts=" + ts).c_str() , content, "application/json");
            retry_time ++;
        }
        nlohmann::json result_json;
        if (res == nullptr) {
            LOG_ERROR("POST lpr response is null, lpr_host=%s", gc_app_config.base_config.lpr_host.c_str());
        } else if (res->status != 200) {
            LOG_ERROR("POST lpr api response status=%d, lpr_host=%s", res->status, gc_app_config.base_config.lpr_host.c_str());
        } else {
            try
            {
                result_json = nlohmann::json::parse(res->body.c_str());
            }
            catch(const std::exception& e)
            {
                LOG_ERROR("POST lpr parse response error, lpr_host=%s, response=%s", gc_app_config.base_config.lpr_host.c_str(), res->body.c_str());
            }
        }

        base64.clear();
        body.clear();
        content.clear();

        logger->info("LprRemote_time: {:.3} s", sw);

        return result_json;
    }

    nlohmann::json GCLpr::lpr_remote_batch(std::vector<std::string>& img_base64_list)
    {
        spdlog::stopwatch sw;

        nlohmann::json result_json;
        nlohmann::json body = {
            {"save", 0},
            {"return_box", 1},
            {"image_list", img_base64_list},
            {"priority_province_chinese", gc_app_config.base_config.alg_config.priority_province_chinese},
            {"priority_province_char", gc_app_config.base_config.alg_config.priority_province_char},
            {"chinese_thres", gc_app_config.base_config.alg_config.chinese_thres},
            {"chinese_thres_for_char", gc_app_config.base_config.alg_config.chinese_thres_for_char},
            // {"detect_model", "retina"},
            // {"plate_resize_type", "perspective"},
            // {"double_detect_by_default_model", 1},
            {"need_detect", gc_app_config.base_config.alg_config.lpr_need_detect},
            {"need_transformer", gc_app_config.base_config.alg_config.lpr_need_transformer},
        };
        std::string content = body.dump();

        struct timespec time1 = {0, 0};
        std::string ts = to_string(time1.tv_sec * 1000 + time1.tv_nsec / 1000000);
        auto res = httpClient()->Post((gc_app_config.base_config.lpr_api + "?ts=" + ts).c_str() , content, "application/json");
        int retry_time = 0;
        while ((res == nullptr || res->status != 200) && retry_time < 3) {
            usleep(100000);
            res = httpClient()->Post((gc_app_config.base_config.lpr_api + "?ts=" + ts).c_str() , content, "application/json");
            retry_time ++;
        }
        if (res == nullptr) {
            LOG_ERROR("POST lpr response is null, lpr_host=%s, img_size=%d, ts=%s", gc_app_config.base_config.lpr_host.c_str(), img_base64_list.size(), ts.c_str());
        } else if (res->status != 200) {
            LOG_ERROR("POST lpr api response status=%d, lpr_host=%s, ts=%s", res->status, gc_app_config.base_config.lpr_host.c_str(), ts.c_str());
        } else {
            try
            {
                result_json = nlohmann::json::parse(res->body.c_str());
            }
            catch(const std::exception& e)
            {
                LOG_ERROR("POST lpr parse response error, lpr_host=%s, response=%s, img_size=%d, ts=%s", gc_app_config.base_config.lpr_host.c_str(), res->body.c_str(), img_base64_list.size(), ts.c_str());
            }
        }

        body.clear();
        content.clear();

        logger->info("LprRemoteBatch_time: {:.3} s", sw);

        return result_json;
    }

    // std::vector<std::string> first_valid_letters_for_green_plate = {"D", "A", "B", "C", "E", "F", "G", "H", "I", "J", "K"};
    // std::vector<std::string> sixth_valid_letters_for_yellow_green_plate = {"D", "A", "B", "C", "E", "F", "G", "H", "I", "J", "K"};
    std::vector<std::string> first_valid_letters_for_green_plate = {"D", "A", "B", "C", "E", "F", "G", "H"};
    std::vector<std::string> sixth_valid_letters_for_yellow_green_plate = {"D", "A", "B", "C", "E", "F"};
    std::vector<std::string> valid_chinese_for_white_plate = {"警"};
    std::vector<std::string> valid_chinese_for_black_plate = {"使", "领", "港", "澳"};
    std::vector<std::string> valid_chinese_for_yellow_plate = {"挂", "学", "试", "超"};    

    nlohmann::json GCLpr::lpr_local(cv::Mat &img, GC_PLATE_TYPE plate_type)
    {
        spdlog::stopwatch sw;

        if (plate_type == GC_PLATE_TYPE::DOUBLE) {
            img = m_forwarder->get_split_merge(img);
        }

        std::string plate = "";
        std::string color = "";
        std::vector<float> char_conf;
        float plate_conf = 0.0;
        if (gc_app_config.base_config.alg_config.local_lpr_type == GC_LOCAL_LPR_TYPE::NCNN) {
            // PlateText_t result;
            // crnn.detect(&img, &result);
            // for (int i = 0; i < result.size; i++)
            // {
            //     char_conf.emplace_back(result.score[i]);
            // }
            // plate_conf = std::accumulate(char_conf.begin(), char_conf.end(), 0.0) / char_conf.size();
            // plate.append(result.text);
        } else if (gc_app_config.base_config.alg_config.local_lpr_type == GC_LOCAL_LPR_TYPE::MNN_CPU) {
            m_forwarder->infer(img, plate, plate_conf, color, char_conf); // color is not used
        } else if (gc_app_config.base_config.alg_config.local_lpr_type == GC_LOCAL_LPR_TYPE::MNN_NNIE) {
            m_nnie.InferenceMat(img, plate, plate_conf, char_conf);
        }

        LOG_INFO("inference plate=%s", plate.c_str());

        bool is_special = false;
        if (plate.rfind("西峡", 0) == 0) {
            is_special = true;
            // 西峡12345格式的车牌，这里处理下，解决最后的电动备案被识别为汉字的情况
            for (auto &ch : PLATE_CHINESE_CHAR_LIST) {
                if (endsWith(plate, ch)) {
                    plate = plate.substr(0, plate.length() - 3);
                    break;
                }
            }
            plate = "西峡" + plate.substr(6);
            color = "绿牌";
        }
        if (!is_special) {
            // 判断车牌是否有效，目前只判断长度
            std::string province = plate.substr(0, 3);
            bool is_plate_valid = plate.length() >= 3 + 6; // TODO 判断汉字数量和位置；判断第二位是否为字母
            if (is_plate_valid) {
                std::string city_char = plate.substr(3, 1);
                // 1. 城市字母匹配优先城市且省份汉字小于设定优先字母阈值则置为优先省份；2. 省份汉字小于设定阈值则置为优先省份
                if (!gc_app_config.base_config.alg_config.priority_province_chinese.empty() && province != gc_app_config.base_config.alg_config.priority_province_chinese) {
                    if (
                            gc_app_config.base_config.alg_config.chinese_thres_for_char > 0 &&
                            city_char == gc_app_config.base_config.alg_config.priority_province_char && char_conf[0] * 100 <= gc_app_config.base_config.alg_config.chinese_thres_for_char
                    ) {
                        plate = gc_app_config.base_config.alg_config.priority_province_chinese + plate.substr(3);
                        // 重新计算置信度
                        char_conf[0] = 0.95;
                        plate_conf = std::accumulate(char_conf.begin(), char_conf.end(), 0.0) / char_conf.size();

                        LOG_INFO("fix plate province by city char, province=%s, fixed_plate=%s", province.c_str(), plate.c_str());

                    } else if (gc_app_config.base_config.alg_config.chinese_thres > 0 && char_conf[0] * 100 <= gc_app_config.base_config.alg_config.chinese_thres) {
                        plate = gc_app_config.base_config.alg_config.priority_province_chinese + plate.substr(3);
                        // 重新计算置信度
                        char_conf[0] = 0.95;
                        plate_conf = std::accumulate(char_conf.begin(), char_conf.end(), 0.0) / char_conf.size();

                        LOG_INFO("fix plate province by low conf, province=%s, fixed_plate=%s", province.c_str(), plate.c_str());
                    }
                }
                // TODO 先根据图片获取一次车牌颜色再根据规则做修订；车牌颜色定义枚举
                // TODO 根据目标检测车牌分类，区分是否为大型车黄牌
                if (plate_type == GC_PLATE_TYPE::DOUBLE) {
                    color = "黄牌";
                } else {
                    if (plate.length() == 3 + 7) {
                        // 起始点去掉第一位中文和第二位城市字母
                        int start_index = 3 + 1;
                        // TODO 根据车牌规则，小车绿牌可以增加以下规则判断：1. 第一位是英文字母 A~K，第二位是英文字母，其他位是阿拉伯数字 2. 第一位是英文字母 A~K，其他位是阿拉伯数字
                        if (std::any_of(first_valid_letters_for_green_plate.begin(), first_valid_letters_for_green_plate.end(), [plate, start_index](std::string letter){ return letter == plate.substr(start_index, 1); })) {
                            color = "绿牌";
                        } 
                        // TODO 大车黄绿车牌规则：第六位是英文字母 A~K，其他位是阿拉伯数字
                        else if (std::any_of(first_valid_letters_for_green_plate.begin(), first_valid_letters_for_green_plate.end(), [plate, start_index](std::string letter){ return letter == plate.substr(start_index + 5, 1); })) {
                            color = "黄绿";
                        }
                    } else if (plate.length() == 3 + 6) {
                        color = "蓝牌";
                    }

                    if (std::any_of(valid_chinese_for_white_plate.begin(), valid_chinese_for_white_plate.end(), [plate](std::string letter){ return plate.find(letter) != std::string::npos; })) {
                        color = "白牌";
                    } else if (std::any_of(valid_chinese_for_black_plate.begin(), valid_chinese_for_black_plate.end(), [plate](std::string letter){ return plate.find(letter) != std::string::npos; })) {
                        color = "黑牌";
                    } else if (std::any_of(valid_chinese_for_yellow_plate.begin(), valid_chinese_for_yellow_plate.end(), [plate](std::string letter){ return plate.find(letter) != std::string::npos; })) {
                        color = "黄牌";
                    }
                    // 不符合规则的车牌置空
                    if (color == "") {
                        LOG_INFO("plate=%s is not in valid format, set to empty", plate.c_str());
                        plate = "";
                        char_conf = {};
                    }
                }
            } else {
                LOG_INFO("plate=%s, color=%s is invalid, set to empty", plate.c_str(), color.c_str());
                color = "";
                plate = "";
                char_conf = {};
            }
        }

        nlohmann::json result_json = {
            { "errcode", 0 },
            { "data", {
                {
                    { "plate", plate.c_str() },
                    { "box", {0, 0, img.cols, img.rows} },
                    { "color", color.c_str() },
                    { "conf", std::lrint(plate_conf * 100) },
                    { "char_conf", char_conf },
                },
            }}
        };

        logger->info("LprLocal_time: {:.3} s, result={}", sw, result_json.dump().c_str());

        return result_json;
    }

    nlohmann::json GCLpr::lpr_local_batch(std::vector<cv::Mat>& img_list)
    {
        std::vector<GC_PLATE_TYPE> plate_type_list;
        return lpr_local_batch(img_list, plate_type_list);
    }

    nlohmann::json GCLpr::lpr_local_batch(std::vector<cv::Mat>& img_list, std::vector<GC_PLATE_TYPE>& plate_type_list)
    {
        spdlog::stopwatch sw;

        nlohmann::json result_json;

        result_json["errcode"] = 0;
        result_json["data"] = nlohmann::json::array();
        int i = 0;
        for (auto &img: img_list) {
            nlohmann::json result = lpr_local(img, plate_type_list.size() >= i ? GC_PLATE_TYPE::SINGLE : plate_type_list[i]);
            result_json["data"].emplace_back(result["data"]);
            i++;
        }

        logger->info("LprLocalBatch_time: {:.3} s", sw);

        return result_json;
    }

    GC_PLATE_COLOR GCLpr::plate_img_color(cv::Mat& img)
    {
        cv::Mat img_hsv;
        cv::cvtColor(img, img_hsv, cv::COLOR_BGR2HSV);



    }
}